﻿using System;
using System.Collections.Generic;

using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;

using RexToy;

namespace ScriptXCompiler
{
    public partial class Translator
    {
        /// <summary>
        /// when generating assign to property code, need set this flag.
        /// </summary>
        bool _isSetter = false;
        bool _rightMost = false;

        protected override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
        {
            this.VisitExpression(node.Expression);
            _output.Write("(");
            this.MakeArgumentsList(node.ArgumentList.Arguments);
            _output.Write(")");
            return node;
        }

        protected override SyntaxNode VisitLiteralExpression(LiteralExpressionSyntax node)
        {
            switch (node.Kind)
            {
                case SyntaxKind.FalseLiteralExpression:
                    _output.Write("false");
                    break;

                case SyntaxKind.TrueLiteralExpression:
                    _output.Write("true");
                    break;

                case SyntaxKind.NullLiteralExpression:
                    _output.Write("null");
                    break;

                case SyntaxKind.NumericLiteralExpression:
                case SyntaxKind.StringLiteralExpression:
                case SyntaxKind.CharacterLiteralExpression:
                    _output.Write(node.GetText());
                    break;

                default:
                    var issue = new CompileIssue(IssueType.Error, IssueId.UnknownExpressionType, node.Kind) { SyntaxItem = node };
                    _aggregator.AppendIssue(issue);
                    break;
            }

            return node;
        }

        protected override SyntaxNode VisitElementAccessExpression(ElementAccessExpressionSyntax node)
        {
            this.VisitExpression(node.Expression);
            _output.Write("[");
            this.MakeArgumentsList(node.ArgumentList.Arguments);
            _output.Write("]");
            return node;
        }

        protected override SyntaxNode VisitBinaryExpression(BinaryExpressionSyntax node)
        {
            if (IsAssignExpression(node) && IsPropertyAccess(node.Left))
            {
                if (IsPartOfAssignment(node))
                {
                    var issue = new CompileIssue(IssueType.Error, IssueId.PropertyGetAndSet) { SyntaxItem = node };
                    _aggregator.AppendIssue(issue);
                    return node;
                }

                if (node.Kind == SyntaxKind.AssignExpression)
                {
                    _isSetter = true;
                    Visit(node.Left);//Note:It's property access, so no need to use VisitExpression
                    _output.Write("(");
                    VisitExpression(node.Right);
                    _output.Write(")");
                }
                else
                {
                    _isSetter = true;
                    Visit(node.Left);//Note:It's property access, so no need to use VisitExpression
                    _output.Write("(");
                    Visit(node.Left);//Note:It's property access, so no need to use VisitExpression
                    _output.Write(" {0} ", this.DecomposeOperator(node.Kind));
                    VisitExpression(node.Right);
                    _output.Write(")");
                }
            }
            else
            {
                switch (node.OperatorToken.Kind)
                {
                    case SyntaxKind.AsKeyword:
                        this.VisitExpression(node.Left);
                        //Note:as operator is used for case, jscript don't care it, ignore.
                        break;

                    case SyntaxKind.QuestionQuestionToken:
                        Visit(node.Left);
                        _output.Write(" || ");
                        Visit(node.Right);
                        break;

                    case SyntaxKind.IsKeyword:
                        Visit(node.Left);
                        _output.Write(" instanceof ");
                        Visit(node.Right);
                        break;

                    default:
                        this.VisitExpression(node.Left);
                        _output.Write(' ' + node.OperatorToken.ValueText + ' ');
                        this.VisitExpression(node.Right);
                        break;
                }
            }

            return node;
        }

        protected override SyntaxNode VisitQualifiedName(QualifiedNameSyntax node)
        {
            var info = _semanticModel.GetSemanticInfo(node);
            _output.Write(this.GetTypeSymbolName(info.Symbol));
            return node;
        }

        protected override SyntaxNode VisitParenthesizedExpression(ParenthesizedExpressionSyntax node)
        {
            _output.Write("(");
            this.VisitExpression(node.Expression);
            _output.Write(")");
            return node;
        }

        protected override SyntaxNode VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node)
        {
            if (node.Kind != SyntaxKind.NegateExpression && node.Kind != SyntaxKind.LogicalNotExpression && this.IsPropertyAccess(node.Operand))
            {
                if (IsPartOfAssignment(node))
                {
                    CompileIssue issue = new CompileIssue(IssueType.Error, IssueId.SelfAssignWithProperty) { SyntaxItem = node };
                    _aggregator.AppendIssue(issue);
                }

                _isSetter = true;
                this.Visit(node.Operand);
                _output.Write("(");
                this.Visit(node.Operand);
                _output.Write(this.TranslateUnaryOperator(node.OperatorToken));
                _output.Write(")");
            }
            else
            {
                _output.Write(node.OperatorToken.ValueText);
                this.Visit(node.Operand);
            }
            return node;
        }

        protected override SyntaxNode VisitPostfixUnaryExpression(PostfixUnaryExpressionSyntax node)
        {
            if (this.IsPropertyAccess(node.Operand))
            {
                if (IsPartOfAssignment(node))
                {
                    CompileIssue issue = new CompileIssue(IssueType.Error, IssueId.PropertyGetAndSet) { SyntaxItem = node };
                    _aggregator.AppendIssue(issue);
                }

                _isSetter = true;
                this.Visit(node.Operand);
                _output.Write("(");
                this.Visit(node.Operand);
                _output.Write(this.TranslateUnaryOperator(node.OperatorToken));
                _output.Write(")");
            }
            else
            {
                this.Visit(node.Operand);
                _output.Write(node.OperatorToken.ValueText);
            }
            return node;
        }

        protected override SyntaxNode VisitThisExpression(ThisExpressionSyntax node)
        {
            _output.Write("this");
            return node;
        }

        protected override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
        {
            var info = _semanticModel.GetSemanticInfo(node.Expression);
            if (info.Symbol != null && info.Symbol.Kind == SymbolKind.NamedType)
            {
                _output.Write(this.GetTypeSymbolName(info.Symbol));
                if (!IsGlobalHolder(node.Expression))
                {
                    _output.Write(".");
                }
                this.Visit(node.Name);
            }
            else
            {
                this.VisitExpression(node.Expression);
                _output.Write(".");

                _rightMost = IsRightMostMemberAccess(node);
                this.Visit(node.Name);
                _rightMost = false;
            }
            return node;
        }

        protected override SyntaxNode VisitObjectCreationExpression(ObjectCreationExpressionSyntax node)
        {
            if (node.InitializerOpt != null)
            {
                if (node.ArgumentListOpt != null && node.ArgumentListOpt.Arguments.Count > 0)
                {
                    var issue = new CompileIssue(IssueType.Error, IssueId.CtorParamWithInitializer) { SyntaxItem = node };
                    _aggregator.AppendIssue(issue);
                }

                _output.Write("{");

                //Extend: Compiler API seems have bug, it can not recognize the parameter name correctly. use below walk around current
                //can not get semantic info of node.Left, shoule be fix in future, because we lost some metadata info(Attributes)
                var count = 0;
                foreach (BinaryExpressionSyntax exp in node.InitializerOpt.Expressions)
                {
                    var left = exp.Left as IdentifierNameSyntax;

                    string name = this.BindSemanticModel(node, left.Identifier);
                    _output.Write("{0}: ", name);
                    //_output.Write("{0}: ", NamingConvention.LowerCase1stChar(left.Identifier.ValueText));
                    this.VisitExpression(exp.Right);

                    count++;
                    if (count < node.InitializerOpt.Expressions.Count)
                    {
                        _output.Write(", ");
                    }
                }

                _output.Write("}");
            }
            else
            {
                var info = _semanticModel.GetSemanticInfo(node);

                if (info.Type.SpecialType == SpecialType.System_Object)
                {
                    _output.Write("{}");
                }
                else if (info.Type.TypeKind == TypeKind.Delegate)
                {
                    this.MakeArgumentsList(node.ArgumentListOpt.Arguments);
                }
                else
                {
                    var name = this.GetTypeSymbolName(info.Type);
                    _output.Write("new " + name + "(");

                    if (node.ArgumentListOpt != null)
                        this.MakeArgumentsList(node.ArgumentListOpt.Arguments);

                    _output.Write(")");
                }
            }

            return node;
        }

        protected override SyntaxNode VisitBaseExpression(BaseExpressionSyntax node)
        {
            var issue = new CompileIssue(IssueType.Error, IssueId.BaseCallNotSupport) { SyntaxItem = node };
            _aggregator.AppendIssue(issue);
            return node;
        }

        protected override SyntaxNode VisitCastExpression(CastExpressionSyntax node)
        {
            //Note:Js don't care type, throw the type info away.
            VisitExpression(node.Expression);
            return node;
        }

        protected override SyntaxNode VisitArrayCreationExpression(ArrayCreationExpressionSyntax node)
        {
            _output.Write("[");

            int count = 0;
            if (node.InitializerOpt != null)
            {
                foreach (var expr in node.InitializerOpt.Expressions)
                {
                    this.VisitExpression(expr);
                    count++;
                    if (count != node.InitializerOpt.Expressions.Count)
                        _output.Write(", ");
                }
            }

            _output.Write("]");

            return node;
        }

        protected override SyntaxNode VisitAnonymousMethodExpression(AnonymousMethodExpressionSyntax node)
        {
            _output.Write("function ({0}) ", this.MakeParametersList(node.ParameterListOpt));
            _output.WriteLine("{");

            _identLevel++;
            this.Visit(node.Block);
            _identLevel--;

            _output.WriteWithIdent(this.CurrLineIdent, "}");
            return node;
        }

        protected override SyntaxNode VisitAnonymousObjectCreationExpression(AnonymousObjectCreationExpressionSyntax node)
        {
            //Extend: not full implement in this version of Roslyn. seems there is bug yet. should be update when new release come.            
            //Same issue as object create initializer -- we can not get symbol info, we lost metadata(attribute)
            _output.Write("{");
            int count = 0;
            foreach (BinaryExpressionSyntax expr in node.Initializer.Expressions)
            {
                var left = expr.Left as IdentifierNameSyntax;
                _output.Write(NamingConvention.LowerCase1stChar(left.Identifier.ValueText));
                _output.Write(": ");

                this.VisitExpression(expr.Right);
                count++;
                if (count != node.Initializer.Expressions.Count)
                    _output.Write(", ");
            }
            _output.Write("}");
            return node;
        }

        protected override SyntaxNode VisitGenericName(GenericNameSyntax node)
        {
            var info = _semanticModel.GetSemanticInfo(node);
            _output.Write(GetMemberSymbolName(info.Symbol));

            return node;
        }

        protected override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
        {
            var info = _semanticModel.GetSemanticInfo(node);
            var symbol = info.Symbol;
            if (symbol == null)
            {
                //Note: the C# compiler already compile this project, so if there is only 1 candidate for the overload resolve fail reason, we can think this is valid symbol
                if (info.CandidateSymbols.Count == 1 && info.CandidateReason == CandidateReason.OverloadResolutionFailure)
                {
                    CompileIssue issue = new CompileIssue(IssueType.Warning, IssueId.OverloadResolveOnlyOne) { SyntaxItem = node };
                    _aggregator.AppendIssue(issue);
                    symbol = info.CandidateSymbols[0];
                }
                else
                {
                    CompileIssue issue = new CompileIssue(IssueType.Error, IssueId.SemanticBind) { SyntaxItem = node };
                    _aggregator.AppendIssue(issue);
                    return node;
                }
            }

            if (!IsScriptSymbol(symbol))
            {
                CompileIssue issue = new CompileIssue(IssueType.Error, IssueId.UseNonScript, info.Type) { SyntaxItem = node };
                _aggregator.AppendIssue(issue);
            }

            switch (symbol.Kind)
            {
                case SymbolKind.Field:
                    if (node.Parent.Kind != SyntaxKind.MemberAccessExpression)
                    {
                        if (symbol.IsStatic)
                        {
                            _output.Write("{0}.", this.GetTypeSymbolName(symbol.ContainingType));
                        }
                        else
                        {
                            _output.Write("this.");
                        }
                    }
                    _output.Write(this.GetMemberSymbolName(symbol));
                    break;

                case SymbolKind.Local:
                case SymbolKind.Parameter:
                    _output.Write(symbol.Name);
                    break;

                case SymbolKind.NamedType:
                    var typeSymbol = symbol as TypeSymbol;
                    var name = this.GetTypeSymbolName(typeSymbol);
                    _output.Write(name);
                    break;

                case SymbolKind.Method:
                    if (!this.IsScriptSymbol(symbol))
                    {
                        CompileIssue issue = new CompileIssue(IssueType.Error, IssueId.CallNonScript) { SyntaxItem = symbol };
                        _aggregator.AppendIssue(issue);
                    }

                    if (node.Parent.Kind != SyntaxKind.MemberAccessExpression)
                    {
                        if (symbol.IsStatic)
                        {
                            _output.Write("{0}.", this.GetTypeSymbolName(symbol.ContainingType));
                        }
                        else
                        {
                            _output.Write("this.");
                        }
                    }
                    _output.Write(this.GetMemberSymbolName(symbol));
                    break;

                case SymbolKind.Property:
                    if (_isSetter && _rightMost)
                    {
                        _output.Write("set{0}", symbol.Name);
                        _isSetter = false;//Note:Reset
                    }
                    else
                    {
                        _output.Write("get{0}()", symbol.Name);
                    }

                    break;

                default:
                    CompileIssue issue2 = new CompileIssue(IssueType.Error, IssueId.UnknownSymbol, symbol.Kind) { SyntaxItem = node };
                    _aggregator.AppendIssue(issue2);
                    break;
            }
            return node;
        }

        protected override SyntaxNode VisitConditionalExpression(ConditionalExpressionSyntax node)
        {
            this.VisitExpression(node.Condition);
            _output.Write(" ? ");
            this.VisitExpression(node.WhenTrue);
            _output.Write(" : ");
            this.VisitExpression(node.WhenFalse);
            return node;
        }

        protected override SyntaxNode VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node)
        {
            _output.Write("function ({0}) ", this.MakeParametersList(node.ParameterList));
            _output.WriteLine("{");
            _identLevel++;
            this.Visit(node.Body);
            _identLevel--;
            _output.WriteWithIdent(this.CurrLineIdent, "}");
            return node;
        }

        protected override SyntaxNode VisitDefaultExpression(DefaultExpressionSyntax node)
        {
            //Note: because we can not predict what T is, unless there is constraint on T said it's class or struct
            CompileIssue issue = new CompileIssue(IssueType.Warning, IssueId.DefaultKeyword) { SyntaxItem = node };
            _aggregator.AppendIssue(issue);
            _output.Write("null");

            return node;
        }
    }
}
